<?php
/**
 * xmarty
 *
 * Небольшой и быстрый PHP фреймворк, использует XML/XSLT
 *
 * @license MIT
 * @version 1.0
 * @date 2010/09/25
 * @author Vladimir Romanovich <ibnteo@gmail.com>
 */

/**
 * Подключение к базе данных
 *
 * Примеры DBCONNECT:
 * mysql:host=localhost; dbname=<database_name>
 * sqlite:<path_to_sqlite_file>
 *
 * @var string Строка подключения к базе данных
 * @var string Логин
 * @var string Пароль
 */
define ('DBCONNECT', '');
define ('DBLOGIN', '');
define ('DBPASS', '');

/**
 * Пароль администратора
 *
 * @var string Пароль или хеш sha1($pass.'xmarty')
 */
define ('ADMIN_PASS', '');

/**
 * Формат даты
 *
 * @var string 'd.m.Y H:i'
 */
define ('DATE_FORMAT', 'd.m.Y H:i');

/**
 * Используется на рабочем сервере
 *
 * @var bool
 */
define ('PRODUCTION', false);

/**
 * Язык файла интернационализации
 *
 * @var string 'ru'
 */
define ('I18N', 'ru');

// Имя стартового файла
if ( ! defined('ACTION')) define('ACTION', 'index');

/**
 * Стартовая функция
 *
 * Запускается первой, инициализирует рабочее окружение
 */
function site_main() {
    global $document, $content, $querys, $times, $i18n;

    // Время начала исполнения скрипта
    $times = microtime(true);

    // Исправить "волшебные" кавычки
    request();

    // Количество SQL запросов для статистики
    $querys = 0;

    // Основной документ для сбора данных
    $document = new SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><doc/>');

    // Абсолютный адрес корня сайта
    $document->addAttribute('base', site_base());

    // Относительный адрес корня сайта
    $document->addAttribute('root', site_root());

    // Адрес откуда пришли на страницу
    if (isset($_SERVER['REDIRECT_URL'])) $document->addAttribute('url', get_server('REDIRECT_URL'));

    // Получение данных через AJAX
    if (isset($_GET['ajax'])) $document->addAttribute('ajax', 1);

    // Сайт на рабочем сервере
    if (defined('PRODUCTION') and PRODUCTION) $document->addAttribute('production', 1);

    // Узел для сбора выводимых данных
    $content = $document->addChild('content');

    // Загрузка файла интернационализации
    $i18n = new SimpleXMLElement('i18n_'.I18N.'.xml', null, true);

    // Подкючиться к базе данных
    sql_connect();
} // end func site_main

/**
 * Base URL
 * 
 * Возвращает абсолютный адрес к корню сайта
 *
 * @return string Base Url
 */
function site_base() {
    $script_name = split('/', get_server('SCRIPT_NAME'));
    array_splice($script_name, 0, 1);
    array_splice($script_name, -2);
    $script_name[] = '';
    return 'http://'.get_server('HTTP_HOST').'/'.join('/', $script_name);
} // end func site_base

/**
 * Относительный путь к корню сайта
 *
 * Возвращает путь от корневой директории к стартовой
 * Нужно при расположении xmarty не в корне сайта
 * 
 * @return string Root path
 */
function site_root() {
    $script_filename = split('/', get_server('SCRIPT_NAME'));
    array_splice($script_filename, 0, 1);
    array_splice($script_filename, -2);
    $script_filename[] = '';
    return '/'.join('/', $script_filename);
} // end func site_root

/**
 * not()
 *
 * @param bool
 * @return bool
 */
function not($bool) {
    return ! $bool;
} // end func not

/**
 * $_POST
 *
 * @param string $name
 * @param bool $is_null Если true, вместо пустого значения будет возвращено null
 * @return string
 */
function get_post($name, $null = false) {
    return isset($_POST[$name]) ? $_POST[$name] : ($null ? null : '');
} // end func get_post

/**
 * $_GET
 */
function get_qs($name) {
    return isset($_GET[$name]) ? $_GET[$name] : '';
} // end func get_qs

/**
 * $_SERVER
 */
function get_server($name) {
    return isset($_SERVER[$name]) ? $_SERVER[$name] : '';
} // end func get_server

/**
 * $_COOKIE
 */
function get_cookie($name) {
    return isset($_COOKIE[$name]) ? $_COOKIE[$name] : '';
} // end func get_cookie

/**
 * Удалить "волшебные" кавычки
 * 
 * В случае неправильной настройки сервера убирает из входящих данных "волшебные" кавычки
 *
 * @param
 * @return
 */
function request() {
    if (get_magic_quotes_runtime()) {
        set_magic_quotes_runtime(0);
    }
    if (get_magic_quotes_gpc()) {
        $_GET = stripslashes_array($_GET);
        $_POST = stripslashes_array($_POST);
        $_COOKIE = stripslashes_array($_COOKIE);
    }
} // end func request

/**
 * Stripslashes array
 */
function stripslashes_array($array) {
    return is_array($array) ? array_map('stripslashes_array', $array) : stripslashes($array);
} // end func stripslashes_array

/**
 * Error 404 Not Found
 *
 * Показывает страницу "Страница не найдена"
 */
function e404() {
    global $document, $i18n;
    header('HTTP/1.1 404 Not Found');
    $document->content->addChild('message', htmlspecialchars($i18n->error->page_not_found));
    response();
} // end func e404

/**
 * Error 403 Forbidden
 *
 * Показывает страницу "Доступ запрещен"
 */
function e403() {
    global $document, $i18n;
    header('HTTP/1.1 403 Forbidden');
    $document->content->addChild('message', htmlspecialchars($i18n->error->access_denied));
    response();
} // end func e403

/**
 * Sendmail
 *
 * Отправляет письмо в кодировке UTF-8, и текстовым содержанием письма
 * 
 * @param string From
 * @param string To
 * @param string Subject
 * @param string Body
 */
function send_mail($from, $to, $subject, $body) {
    $subject = '=?UTF-8?B?'.base64_encode($subject).'?=';
    $headers = 'From: '.$from."\n";
    $headers .= 'Content-Type: text/plain; charset=utf-8'."\n";
    $headers .= 'Content-Transfer-Encoding: 8bit';
    $result = @mail($to, $subject, $body, $headers);
    return $result;
} // end func send_mail

/**
 * Компактный список страниц
 *
 * @param int $page Номер страницы
 * @param int $count Количество записей на странице
 * @param int $records Количество записей в выборке
 * @param SimpleXMLNode узел, куда будуд вставлены данные <pages>
 */
function pages($page, $count, $records, $node) {
    $pages = $node->addChild('pages');
    $count_pages = ceil($records / $count);
    $pages->addAttribute('count', $count_pages);
    $pages->addAttribute('page', $page);

    if ($count_pages > 7 and $page == 0) {
        for($i = 1; $i <= 3 ; $i ++) {
            $pages->addChild('page', $i);
        }
        $pages->addChild('page', '..');
        for($i = ($count_pages - 2); $i <= $count_pages; $i ++) {
            $pages->addChild('page', $i);
        }
    } else if ($count_pages > 7 and $page < 5) {
        for($i = 1; $i <= 5; $i ++) {
            $pages->addChild('page', $i);
        }
        $pages->addChild('page', '..');
        $pages->addChild('page', $count_pages);
    } else if ($count_pages > 7 and $page > ($count_pages - 4)) {
        $pages->addChild('page', 1);
        $pages->addChild('page', '..');
        for($i = ($count_pages - 4); $i <= $count_pages ; $i ++) {
            $pages->addChild('page', $i);
        }
    } else if ($count_pages > 8 and $page > 4 and $page < ($count_pages - 3)) {
        $pages->addChild('page', 1);
        $pages->addChild('page', '..');
        for($i = ($page - 1); $i <= ($page + 1); $i ++) {
            $pages->addChild('page', $i);
        }
        $pages->addChild('page', '..');
        $pages->addChild('page', $count_pages);
    } else {
        for($i = 1; $i <= $count_pages; $i ++) {
            $pages->addChild('page', $i);
        }
    }
} // end func pages

/**
 * Подключение к базе данных
 *
 * База данных должна использовать UTF-8 кодировку
 */
function sql_connect() {
    global $pdo;
    if (DBCONNECT) {
        $pdo = new PDO(DBCONNECT, DBLOGIN, DBPASS);
        $pdo->exec('set character set utf8');
        $pdo->exec('set names utf8');
    }
} // end func sql_connect

/**
 * SQL Value
 *
 * Возвращает значение или массив значений первой записи
 * Для передачи параметров необходимо использовать знак вопроса "?"
 *
 * @param string SQL запрос
 * @param array Массив параметров
 * @return bool Результат выполнения запроса
 */
function sql_value($sql, $param = null) {
    global $pdo, $querys;
    $querys ++;
    $result = null;
    $query = $pdo->prepare($sql);
    $query->execute($param);
    if ($row = $query->fetchObject()) {
        $param = array();
        $count = 0;
        foreach ($row as $key => $val) {
            $count ++;
            $result = $val;
            if (strstr($key, 'date') !== false)
                $val = date(DATE_FORMAT, strtotime($val));
            $param[$key] = $val;
        }
        if ($count > 1) $result = $param;
    }
    unset($query);
    return $result;
} // end func sql_value

/**
 * SQL Execute
 *
 * Выполнить SQL запрос
 *
 * @param string SQL запрос
 * @param array Массив параметров
 * @param bool Если true, возвращает LastID
 * @return bool Результат выполнения запроса
 * @return int В случае $lastid==true возвращает LastID
 */
function sql($sql, $param = null, $lastid = false) {
    global $pdo, $querys;
    $querys ++;
    $query = $pdo->prepare($sql);
    $result = $query->execute($param);
    if ($row = $query->fetchObject()) {
        $result = $row;
    }
    unset($query);
    return $lastid ? $pdo->lastInsertId() : $result;
} // end func sql

/**
 * SQL Items
 *
 * Вставляет в SimpleXML документ результат SQL запроса в тегах <item>
 * 
 * @param SimleXMLNode Узел документа, куда будут вставленны данные
 * @param string SQL запрос
 * @param array Массив параметров
 * @return bool Результат выполнения запроса
 */
function sql_items($node, $sql, $param = null) {
    global $pdo, $querys;
    $querys ++;
    $query = $pdo->prepare($sql);
    $result = $query->execute($param);
    while ($row = $query->fetchObject()) {
        $item = $node->addChild('item');
        foreach ($row as $key => $value) {
            sql_field_value($item, $key, $value);
        }
    }
    unset ($query);
    return $result;
} // end func sql_items

/**
 * SQL One
 *
 * Вставить в SimpleXML документ данные SQL запроса
 * 
 * @param SimleXMLNode Узел документа, куда будут вставлены данные
 * @param string SQL запрос
 * @param array Массив параметров
 */
function sql_one($node, $sql, $param = null) {
    global $pdo, $querys;
    $querys ++;
    $query = $pdo->prepare($sql);
    $result = $query->execute($param);
    while ($row = $query->fetchObject()) {
        foreach($row as $key => $value) {
            sql_field_value($node, $key, $value);
        }
    }
    unset($query);
    return $result;
} // enc func sql_one

/**
 * SQL Field Data
 *
 * Вернуть значение в зависимости от типа данных
 * 
 * @param SimleXMLNode Узел документа, куда будут вставлены данные
 * @param string SQL запрос
 * @param array Массив параметров
 */
function sql_field_value($node, $key, $value) {
    $val = $value;
    // Date
    if (strstr($key, 'date') !== false) {
        if ($value) {
            $value = date(DATE_FORMAT, strtotime($value));
        }
        $item = $node->addChild($key, htmlspecialchars($value));
    } elseif ($key == 'text') {
        $text = $value;
        $text = str_replace('&', '&amp;', $text);
        $text = str_replace('<', '&lt;', $text);
        $text = str_replace("\r", '', $text);
        $text = preg_replace('/\n+/um', "\n", $text);
        $text = preg_replace('/\t/mu', '&#160;&#160;', $text);
        $text = preg_replace('/\n\t/mu', "\n&#160;", $text);
        $text = str_replace("\n", '<br />', $text);
        $text = preg_replace('%(http://[^ ,!<\n]+)%ui', '<a href="$1">$1</a>', $text);
        $item = $node->addChild($key, htmlspecialchars($value));
        $item->addAttribute('html', $text);
    } else {
        $node->addChild($key, htmlspecialchars($value));
    }
} // end func sql_field_value

/**
 * Response
 *
 * Производит XSTL трансформацию документа и вывод результата в выходной поток
 *
 * @param string Имя файла шаблона (без расширения)
 */
function response($template = 'index') {
    global $document, $pages, $times, $querys, $i18n;
    header('Content-Type: text/html; charset=utf-8');

    // Отобразить статистику на тестовом сервере
    if (defined('PRODUCTION') and not(PRODUCTION)) {
        $stat = $document->addChild('stat');
        $stat->addAttribute('time', microtime(true) - $times);
        $stat->addAttribute('querys', $querys);
        $stat->addAttribute('memory', memory_get_usage() / (1024 * 1024));
    }

    // Сформировать документ
    $doc = new DomDocument();
    $doc->loadXml($document->asXml());

    // Загрузить I18n языковой файл
    $doc_lang = new DomDocument();
    $doc_lang->loadXml($i18n->asXml());
    $doc->documentElement->appendChild($doc->importNode($doc_lang->documentElement, true));

    // Загрузить XSLT шаблон
    $xslt = new DomDocument();
    $xslt->load($template.'.xslt', LIBXML_DTDLOAD + LIBXML_NOENT);

    $proc = new XsltProcessor();
    $proc->importStylesheet($xslt);

    // Произвести XSTL трансформацию в выходной поток
    $proc->transformToUri($doc, 'php://output');
    exit;
} // end func response

/**
 * Action Index
 *
 * Отобразить стартовую страницу
 */
function action_index() {
    global $document, $content;
    $content->addChild('index');
    response();
} // end func action_index

// Выполнить стартовую функцию
site_main();

// Выполнить, если вызвали index.php
if (ACTION == 'index') {
    action_index();
}

// end index.php
